package edu.mercer.sse.bkp;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.google.common.base.Optional;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Range;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkArgument;

/**
 * Concrete Command class of Command Pattern.<p>
 *
 * SnappyCompression class used as Receiver in this Command Pattern.<p>
 * 
 * Supplies executor. Masks complexity of running, returning listenable 
 * futures without blocking for output: running asynchronously.<p>
 * 
 * Provides indirection for execute(), progress() % completed, and additional
 * 	functionality not in Receiver: undo.<p>
 *
 * Also: Leaf as in Composite Pattern.<p>
 * 
 * @see CompressCommandComposite
 */

public class CompressCommand implements 
		
	CommandRecall<ListenableFuture
					<TaskResults<ImmutableMap<String, String>>>, 
					Boolean>, 
		
	CommandProgress<ListenableFuture
						<TaskResults<ImmutableMap<String, String>>>, 
				   Float>,
				   
	Comparable<CompressCommand>
				   
{
	/** SnappyCompression combined Future/Callable task */
	
	private Optional<SnappyCompression> _task = Optional.absent();
	
	/** ExecutorService */
	
	private final static ExecutorService _excPool = Executors
			.newCachedThreadPool();
	
	/** ListeningExecutorService Guava ExecutorService Decorator */
	
	private final static ListeningExecutorService _threadExe = MoreExecutors
			.listeningDecorator(_excPool);
	

	/**
	 * CompressCommand private no-arg constructor for factory usage.<p>
	 */

	private CompressCommand()
	{
		super();
		
	}//constructor
	

	/**
	 * CompressCommand constructor.<p>
	 * 
	 * @param taskIn - SnappyCompression task for execution
	 */
	
	public CompressCommand( final SnappyCompression taskIn )
	{
		super();

		checkNotNull( taskIn, "null detected: SnappyCompression argument" );

		_task = Optional.of( taskIn );
		
	}//constructor
	
	
	/**
	 * Set underlying SnappyCompression task of this CompressCommand / 
	 * 	Command Pattern wrapper class.<p>
	 * 
	 * Required for construction.<p>
	 * 
	 * @param taskIn - SnappyCompression task for execution
	 * 
	 * @return  this
	 */
	
	public CompressCommand withTask( final SnappyCompression taskIn )
	{
		_task = Optional.of( taskIn );
		
		return this;
		
	}//method
	
	
	/**
	 * CompressCommand Factory.<p>
	 * 
	 * @return  new CompressCommand instance
	 */
	
	public static CompressCommand factory()
	{
		return new CompressCommand();
		
	}//method
	

	@Fulfills("Command")
	@Override
	public ListenableFuture<TaskResults<ImmutableMap<String, String>>> 
		execute() 
	{
		checkArgument( _task.isPresent(), 
				"SnappyCompression required: use withTask method" );
		
		return _threadExe.submit( _task.get() );

	}//method

	
	/**
	 * Restore file to uncompressed form. Remove partially compressed files.
	 * <p>
	 * 
	 * Additional functionality apart from SnappyCompression task. Suited for
	 * 	Command as has undo method.<p>
	 */
	
	@Fulfills("CommandRecall")
	@Override
	public Boolean undo() 
	{
		final String logName = Settings.access()
									   .getProperty("LOG-FILE");
		
		final CustomLogger log = LogCustom.obtainLog( logName );
		
		final File compressedFile = Paths.get( _task
				.get()
				.fileCompressed() )
				.toFile();

		final File unCompressedFile = Paths.get( _task
				.get()
				.fileUncompressed() )
				.toFile();

		final Boolean purgeAfterCompress = Boolean.valueOf( Settings
				.access()
				.getProperty("REMOVE-UNCOMPRESSED") );

		if( ! _task.get().isDone() )
		{ 
			log.warning("Cannot undo task: still active");
			
			return false;
			
		}//if
		
		Boolean result = false;
		
		
		if( ! purgeAfterCompress )
		{
			result = compressedFile.delete();
		
			result = ! compressedFile.exists();
			
		}
		else
		{
			/* original file gone, attempt uncompress to restore */
			
			try 
			{
				log.info( "attempting restore for undo: " 
						+ unCompressedFile 
						+ " using " 
						+ compressedFile );
				
				_task.get().restoreOK(true);
				
				_task.get().unCompressFile();

				result = unCompressedFile.exists();

				if(!result) 
					log.severe("uncompressed file not exists: " 
							+ unCompressedFile);
				
				result = unCompressedFile.length() 	> 
						   compressedFile.length();

				if(!result) 
					log.severe( "uncompressed file length: " 
							+ unCompressedFile.length() 
							+ " not larger than " 
							+ "compressed file length: " 
							+ compressedFile.length() );
						   
				if( result ) result = compressedFile.delete();
				
			} 
			catch( final IOException         |
						 InterruptedException err ) 
			{
				new BackupException(err).accept( 
						BackupErrorLoggingVisitor.factory() );
				
			}//try
			
		}//if

		return result;
		
	}//method

	
	@Fulfills("CommandProgress")
	@Override
	public Float progress() 
	{
		Float percentDone =  _task.get().compressCompleted();
		
		if( percentDone == null ) percentDone = 0F;
		
		return percentDone;

	}//method


	/** Stop ListeningExecutorService and ExecutorService.<p> */
	
	public static void shutdownExecutor()
	{
		SnappyCompression.shutdownAnExecutor(_threadExe);
		SnappyCompression.shutdownAnExecutor(_excPool);

	}//method

	
	@Fulfills("CommandProgress")
	@Override	
	public String textProgressMeter()
	{
		return textProgressMeter( _task.get()
									   .fileUncompressed(), 
									   
								  this.progress() );

	}//method


	/**
	 * Progress text based meter.<p>
	 * 
	 * Example:
	 * 
	 * <pre>Core Java Volume I.pdf |*********                 | 31.8%</pre>
	 * 
	 * @param legend  String displayed to left of meter
	 * 
	 * @param progressPercentage  Float between 0 and 1
	 * 
	 * @param widthIn  Integer, nonmandatory
	 * 
	 * @return  String containing text based meter
	 */
	
	public static String textProgressMeter( final String legend,
											final Float progressPercentage,
											final Integer... widthIn ) 
	{
		String meter = "";

		Integer width = 0;
		
		Range<Float> validPercentage = Range.closed(0F, 1F);
		
		checkArgument( validPercentage.contains(progressPercentage), 
				"invalid progress percentage: " + progressPercentage 
				+ " expected: " 
				+ validPercentage );
		
		if( widthIn.length < 1 )
		{ 
			width = Integer.valueOf( Settings
									  .access()
									  .getProperty("PROGRESS-METER-WIDTH") );
			
		}
		else
		{
			width = widthIn[0];
			
		}//if

		meter += legend + " "; 

		Integer completed = Math.round(progressPercentage*width);

		meter += "|";

		for ( Integer i=0; i <= completed; i++ ) meter += "*";

		for (Integer i=completed; i < width; i++) meter += " ";

		meter += "|" + String.format(" %.1f%%", progressPercentage * 100 );

		return meter;

	}//method


	@Fulfills("Comparable")
	@Override
	public int compareTo( final CompressCommand other ) 
	{
		return ComparisonChain
				.start()
				.compare(
				
					this._task
						.get()
						.fileUncompressed(), 
				
					other._task
						 .get()
						 .fileUncompressed())
				
				.result();
	
	}//method


	/** Uncompressed filename, unique to each SnappyCompression task. */

	@Fulfills("Object")
	@Override
	public String toString()
	{
		return _task.get()
					.fileUncompressed();
		
	}//method
	
	  
}//class
